#region Using

using System;
using System.Text;
using System.Web;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Collections.Generic;
using BlogEngine.Core;
using System.Threading;

#endregion

namespace BlogEngine.Core.Web.HttpModules
{
	/// <summary>
	/// Handles pretty URL's and redirects them to the permalinks.
	/// </summary>
	public class UrlRewrite : IHttpModule
	{
		#region IHttpModule Members

		/// <summary>
		/// 
		/// </summary>
		public void Dispose()
		{
			// Nothing to dispose
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		public void Init(HttpApplication context)
		{
			context.BeginRequest += new EventHandler(context_BeginRequest);
		}

		#endregion

		/// <summary>
		/// Handles the BeginRequest event of the context control.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
		private void context_BeginRequest(object sender, EventArgs e)
		{
			HttpContext context = ((HttpApplication)sender).Context;

            SetThreadCulture(context);

			string path = context.Request.Path.ToUpperInvariant();
            string url = context.Request.RawUrl.ToUpperInvariant();

            // HACK: Cultures
            if (Utils.ContainsLanguage(url) &&
                (url.Contains(BlogSettings.Instance.FileExtension.ToUpperInvariant()) ||
                url.Contains("BLOGIMPORTER.ASMX") ||
                url.Contains("SYNDICATION.AXD") ||
                url.Contains("RSD.AXD") ||
                url.Contains("BLOGML.AXD") ||
                url.Contains("SIOC.AXD") ||
                url.Contains("TRACKBACK.AXD")) &&
                !url.Contains("ERROR404.ASPX"))
            {
                if (path == Utils.RelativeWebRoot.ToUpperInvariant() + Utils.CurrentLanguage.ToUpperInvariant() + "/" + "DEFAULT.ASPX" &&
                    context.Request.QueryString.Count == 0)
                {
                    Page front = Page.GetFrontPage();
                    if (front != null)
                        url = front.RelativeLink.ToUpperInvariant();
                }

                if (path.Contains("/BLOGIMPORTER.ASMX"))
                {
                    SetCulture(Regex.Match(url, "/[a-z]{2}(-[a-z]{2})?/", RegexOptions.IgnoreCase).Value.Trim('/'));
                    context.RewritePath(Utils.RelativeWebRoot + "api/blogimporter.asmx");
                }
                else if (path.Contains("/TRACKBACK.AXD") ||
                    path.Contains("/RSD.AXD") ||
                    path.Contains("/BLOGML.AXD") ||
                    path.Contains("/SIOC.AXD") ||
                    path.Contains("/SYNDICATION.AXD"))
                {
                    SetCulture(Regex.Match(url, "/[a-z]{2}(-[a-z]{2})?/", RegexOptions.IgnoreCase).Value.Trim('/'));
                    context.RewritePath(Utils.RelativeWebRoot + context.Request.RawUrl.Replace(Utils.RelativeWebRoot + Utils.CurrentLanguage + "/", ""));
                }
                else if (path.Contains("/BLOG.ASPX"))
                {
                    context.RewritePath(Utils.RelativeWebRoot + "default.aspx?blog=true" + GetQueryString(context));
                }
                else if (url.Contains("/POST/"))
                {
                    RewritePost(context, url);
                }
                else if (url.Contains("/CATEGORY/"))
                {
                    RewriteCategory(context, url);
                }
                else if (url.Contains("/PAGE/"))
                {
                    RewritePage(context, url);
                }
                else if (url.Contains("/CALENDAR/"))
                {
                    context.RewritePath(Utils.RelativeWebRoot + "default.aspx?calendar=show", false);
                }
                else if (url.Contains("/DEFAULT" + BlogSettings.Instance.FileExtension.ToUpperInvariant()))
                {
                    RewriteDefault(context);
                }
                else if (url.Contains("/AUTHOR/"))
                {
                    string author = ExtractTitle(context, url);
                    context.RewritePath(Utils.RelativeWebRoot + "default" + BlogSettings.Instance.FileExtension + "?name=" + author + GetQueryString(context), false);
                }
                else
                {
                    context.RewritePath(Regex.Replace(context.Request.RawUrl, "/[a-z]{2}(-[a-z]{2})?/", "/", RegexOptions.IgnoreCase));
                }
            } // HACK: Cultures
            else if ((url.Contains(BlogSettings.Instance.FileExtension.ToUpperInvariant()) ||
                url.Contains("BLOGIMPORTER.ASMX") ||
                url.Contains("SYNDICATION.AXD") ||
                url.Contains("RSD.AXD") ||
                url.Contains("BLOGML.AXD") ||
                url.Contains("SIOC.AXD") ||
                url.Contains("TRACKBACK.AXD")) &&
                !url.Contains("ERROR404.ASPX") && !url.Contains("/ADMIN/"))
                context.Response.Redirect(context.Request.RawUrl.Replace(Utils.RelativeWebRoot, Utils.RelativeWebRoot + Utils.CurrentLanguage + "/"));
            else if (context.Request.Path.EndsWith("/") && !url.Contains("ERROR404.ASPX") && !url.Contains("/ADMIN/"))
                context.Response.Redirect(context.Request.Path + "default.aspx?" + context.Request.QueryString.ToString());
            else if (url.ToUpperInvariant().EndsWith("/" + Utils.CurrentLanguage.ToUpperInvariant()) && !url.Contains("ERROR404.ASPX") && !url.Contains("/ADMIN/"))
                context.Response.Redirect(context.Request.Path + "/default.aspx?" + context.Request.QueryString.ToString());
		}

		/// <summary>
		/// Sets thread culture based on various input values
		/// </summary>
        /// <param name="context">Current HTTP context.</param>
        private void SetThreadCulture(HttpContext context)
        {
            string path = context.Request.RawUrl.ToUpperInvariant();

            if (context.Request.QueryString.ToString() == "setculture")
            {
                HttpCookie cookie = context.Request.Cookies["frontend_language"];
                if (cookie == null)
                {
                    cookie = new HttpCookie("frontend_language");
                }
                if (Regex.IsMatch(context.Request.RawUrl, "^" + Utils.RelativeWebRoot + "[a-z]{2}(-[a-z]{2})?/", RegexOptions.IgnoreCase))
                    cookie["culture"] = Regex.Match(context.Request.RawUrl, "^" + Utils.RelativeWebRoot + "([a-z]{2}(-[a-z]{2})?)/", RegexOptions.IgnoreCase).Groups[1].Value.Trim('/');
                cookie.Expires = DateTime.Now.AddYears(3);
                context.Response.Cookies.Add(cookie);
            }

            if (path.Contains("/ADMIN/"))
            {
                if (!string.IsNullOrEmpty(BlogSettings.Instance.Culture))
                {
                    if (!BlogSettings.Instance.Culture.Equals("Auto"))
                    {
                        SetCulture(BlogSettings.Instance.Culture);
                    }
                }
            }
            else if (Regex.IsMatch(path, "^" + Utils.RelativeWebRoot.ToUpperInvariant() + "[A-Z]{2}(-[A-Z]{2})?/"))
            {
                SetCulture(Regex.Match(path, "^" + Utils.RelativeWebRoot.ToUpperInvariant() + "([A-Z]{2}(-[A-Z]{2})?)/").Groups[1].Value.Trim('/'));
            }
            else
            {
                HttpCookie cookie = context.Request.Cookies["frontend_language"];

                if (cookie != null)
                {
                    SetCulture(cookie["culture"]);
                }
                else if (context.Request.UserLanguages != null && context.Request.UserLanguages.Length > 0)
                {
                    Regex languageMatch = new Regex("[A-Z]{2}(-[A-Z]{2})?", RegexOptions.IgnoreCase);

                    foreach (string userLanguage in context.Request.UserLanguages)
                    {
                        if (BlogSettings.Instance.CultureFrontend[languageMatch.Match(userLanguage).Value] != null)
                        {
                            SetCulture(languageMatch.Match(userLanguage).Value);
                            cookie = new HttpCookie("frontend_language");
                            cookie["culture"] = languageMatch.Match(userLanguage).Value;
                            cookie.Expires = DateTime.Now.AddYears(3);
                            context.Response.Cookies.Add(cookie);
                            break;
                        }
                    }
                }
                else
                {
                    foreach (CultureCollectionItem item in BlogSettings.Instance.CultureFrontend)
                    {
                        if (item.Active)
                        {
                            SetCulture(item.CultureName);
                            cookie = new HttpCookie("frontend_language");
                            cookie["culture"] = item.CultureName;
                            cookie.Expires = DateTime.Now.AddYears(3);
                            context.Response.Cookies.Add(cookie);
                            break;
                        }
                    }
                }
            }
        }

		/// <summary>
		/// Sets culture to current thread.
		/// </summary>
		/// <param name="cultureName">Culture name (cs, fr, en, etc.)</param>
        private void SetCulture(string cultureName)
        {
            CultureInfo culture = CultureInfo.CreateSpecificCulture(cultureName);
            Thread.CurrentThread.CurrentUICulture = culture;
            Thread.CurrentThread.CurrentCulture = culture;
        }

		private static void RewritePost(HttpContext context, string url)
		{
			DateTime date = ExtractDate(context);
			string slug = ExtractTitle(context, url);
			Post post = Post.Posts.Find(delegate(Post p)
			{
				if (date != DateTime.MinValue && (p.DateCreated.Year != date.Year || p.DateCreated.Month != date.Month))
				{
					if (p.DateCreated.Day != 1 && p.DateCreated.Day != date.Day)
						return false;
				}

				return slug.Equals(Utils.RemoveIllegalCharacters(p.Slug), StringComparison.OrdinalIgnoreCase);
			});

			if (post != null)
			{
				context.RewritePath(Utils.RelativeWebRoot + "post.aspx?id=" + post.Id.ToString() + GetQueryString(context), false);
			}
		}

		private static void RewriteCategory(HttpContext context, string url)
		{
			string title = ExtractTitle(context, url);
			foreach (Category cat in Category.Categories)
			{
				string legalTitle = Utils.RemoveIllegalCharacters(cat.Title).ToLowerInvariant();
				if (title.Equals(legalTitle, StringComparison.OrdinalIgnoreCase))
				{
					context.RewritePath(Utils.RelativeWebRoot + "default.aspx?id=" + cat.Id.ToString() + GetQueryString(context), false);
					break;
				}
			}
		}

		private static void RewritePage(HttpContext context, string url)
		{
			string slug = ExtractTitle(context, url);
			Page page = Page.Pages.Find(delegate(Page p)
			{
				return slug.Equals(Utils.RemoveIllegalCharacters(p.Slug), StringComparison.OrdinalIgnoreCase);
			});

			if (page != null)
			{
				context.RewritePath(Utils.RelativeWebRoot + "page.aspx?id=" + page.Id + GetQueryString(context), false);
			}
		}

		private static readonly Regex YEAR = new Regex("/([0-9][0-9][0-9][0-9])/", RegexOptions.IgnoreCase | RegexOptions.Compiled);
		private static readonly Regex YEAR_MONTH = new Regex("/([0-9][0-9][0-9][0-9])/([0-1][0-9])/", RegexOptions.IgnoreCase | RegexOptions.Compiled);
		private static readonly Regex YEAR_MONTH_DAY = new Regex("/([0-9][0-9][0-9][0-9])/([0-1][0-9])/([0-3][0-9])/", RegexOptions.IgnoreCase | RegexOptions.Compiled);

		private static void RewriteDefault(HttpContext context)
		{
			string url = context.Request.RawUrl;
			string page = "&page=" + context.Request.QueryString["page"];
			if (string.IsNullOrEmpty(context.Request.QueryString["page"]))
				page = null;

			if (YEAR_MONTH_DAY.IsMatch(url))
			{
				Match match = YEAR_MONTH_DAY.Match(url);
				string year = match.Groups[1].Value;
				string month = match.Groups[2].Value;
				string day = match.Groups[3].Value;
				string date = year + "-" + month + "-" + day;
				context.RewritePath(Utils.RelativeWebRoot + "default.aspx?date=" + date + page, false);
			}
			else if (YEAR_MONTH.IsMatch(url))
			{
				Match match = YEAR_MONTH.Match(url);
				string year = match.Groups[1].Value;
				string month = match.Groups[2].Value;
				string path = string.Format("default.aspx?year={0}&month={1}", year, month);
				context.RewritePath(Utils.RelativeWebRoot + path + page, false);
			}
            else if (YEAR.IsMatch(url))
            {
                Match match = YEAR.Match(url);
                string year = match.Groups[1].Value;
                string path = string.Format("default.aspx?year={0}", year);
                context.RewritePath(Utils.RelativeWebRoot + path + page, false);
            }
            else
            {
                context.RewritePath(Utils.RelativeWebRoot + "default.aspx");
            }
		}

		/// <summary>
		/// Extracts the title from the requested URL.
		/// </summary>
		private static string ExtractTitle(HttpContext context, string url)
		{
			url = url.ToLowerInvariant().Replace("---", "-");
			if (url.Contains(BlogSettings.Instance.FileExtension) && url.EndsWith("/"))
			{
				url = url.Substring(0, url.Length - 1);
				context.Response.AppendHeader("location", url);
				context.Response.StatusCode = 301;
			}

			url = url.Substring(0, url.IndexOf(BlogSettings.Instance.FileExtension));
			int index = url.LastIndexOf("/") + 1;
			string title = url.Substring(index);
			return context.Server.UrlEncode(title);
		}

		/// <summary>
		/// Extracts the year and month from the requested URL and returns that as a DateTime.
		/// </summary>
		private static DateTime ExtractDate(HttpContext context)
		{
			if (!BlogSettings.Instance.TimeStampPostLinks)
				return DateTime.MinValue;

			Match match = YEAR_MONTH_DAY.Match(context.Request.RawUrl);
			if (match.Success)
			{
				int year = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
				int month = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
				int day = int.Parse(match.Groups[3].Value, CultureInfo.InvariantCulture);
				return new DateTime(year, month, day);
			}

			match = YEAR_MONTH.Match(context.Request.RawUrl);
			if (match.Success)
			{
				int year = int.Parse(match.Groups[1].Value, CultureInfo.InvariantCulture);
				int month = int.Parse(match.Groups[2].Value, CultureInfo.InvariantCulture);
				return new DateTime(year, month, 1);
			}

			return DateTime.MinValue;
		}

		/// <summary>
		/// Gets the query string from the requested URL.
		/// </summary>
		/// <param name="context">The context.</param>
		/// <returns></returns>
		private static string GetQueryString(HttpContext context)
		{
			string query = context.Request.QueryString.ToString();
			if (!string.IsNullOrEmpty(query))
				return "&" + query;

			return string.Empty;
		}
	}
}